/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice;

import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.base.AbstractDelegateConsumer;
import org.proteusframework.core.base.Namespace;
import org.proteusframework.core.util.Assert;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * Provides custom service scoped instance management on behalf of the {@link ExtendedProteusApplication}.
 */
@SuppressWarnings({"unchecked"})
final class ServiceScopeManager extends AbstractDelegateConsumer implements IScopedServiceDelegate
{
    private static final Logger logger = Logger.getLogger(ServiceScopeManager.class.getCanonicalName());

    private Map<Class<? extends IServicePlugin>, Map<ServiceScope, List<ScopedServiceInstance>>> lookAtThisCrazyPhuckingMapDeclaration
            = new ConcurrentHashMap<Class<? extends IServicePlugin>, Map<ServiceScope, List<ScopedServiceInstance>>>();

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IServicePlugin> T getPlatformService(Class<T> expectedInterface, ServiceScope scope) throws ServiceScopingException
    {
        T instance = null;

        Assert.parameterNotNull(expectedInterface, "Parameter 'expectedInterface' must not be null");
        Assert.parameterNotNull(scope, "Parameter 'scope' must not be null");

        switch (scope)
        {
            case NewInstancePerRequest:

                T newInstanceForRequest = (T) initializeService(expectedInterface);

                if (newInstanceForRequest.getRestrictedServiceScopes().contains(scope))
                {
                    throw new ServiceScopingException("Custom service " + Namespace.toCanonical(newInstanceForRequest) + " explicitly restricts the use of the scope " + scope);
                }

                newInstanceForRequest.setPlatformDelegate(delegate, IExtendedPlatformDelegate.class);
                newInstanceForRequest.start();

                instance = newInstanceForRequest;

                break;
            case ApplicationSingleton:

                T newInstanceForAppSingleton = (T) initializeService(expectedInterface);

                if (newInstanceForAppSingleton.getRestrictedServiceScopes().contains(scope))
                {
                    throw new ServiceScopingException("Custom service " + Namespace.toCanonical(newInstanceForAppSingleton) + " explicitly restricts the use of the scope " + scope);
                }

                Map<ServiceScope, List<ScopedServiceInstance>> scopeListMap = lookAtThisCrazyPhuckingMapDeclaration.get(expectedInterface);

                if (null != scopeListMap)
                {
                    List<ScopedServiceInstance> scopedServiceInstances = scopeListMap.get(scope);
                    if (null != scopedServiceInstances)
                    {
                        for (ScopedServiceInstance curInstance : scopedServiceInstances)
                        {
                            if (curInstance.getServiceScope() == scope)
                            {
                                //noinspection unchecked
                                instance = (T) curInstance.getInstance();
                                break;
                            }
                        }

                        if (null == instance)
                        {
                            // First request at this scope, but not the first request ever for this custom service plugin
                            newInstanceForAppSingleton.setPlatformDelegate(delegate, IExtendedPlatformDelegate.class);
                            newInstanceForAppSingleton.start();

                            ScopedServiceInstance<T> ssi = new ScopedServiceInstance<T>(expectedInterface, scope, newInstanceForAppSingleton);

                            List<ScopedServiceInstance> list = new ArrayList<ScopedServiceInstance>();
                            list.add(ssi);

                            scopeListMap.put(scope, list);

                            instance = ssi.getInstance();
                        }
                    } else
                    {
                        throw new IllegalStateException("Service plugin was registered, but contained a null scoped service instance list");
                    }
                } else
                {
                    // First request ever (at any scope) for this custom service plugin
                    newInstanceForAppSingleton.setPlatformDelegate(delegate, IExtendedPlatformDelegate.class);
                    newInstanceForAppSingleton.start();
                    ScopedServiceInstance<T> ssi = new ScopedServiceInstance<T>(expectedInterface, scope, newInstanceForAppSingleton);

                    List<ScopedServiceInstance> list = new ArrayList<ScopedServiceInstance>();
                    list.add(ssi);

                    Map<ServiceScope, List<ScopedServiceInstance>> serviceScopeMap = new HashMap<ServiceScope, List<ScopedServiceInstance>>();
                    serviceScopeMap.put(scope, list);

                    lookAtThisCrazyPhuckingMapDeclaration.put(expectedInterface, serviceScopeMap);

                    instance = ssi.getInstance();
                }
                break;
            default:
                throw new ServiceScopingException("ServiceScope " + scope.name() + " requires an INamespace declaration. Please invoke the overloaded method to provide a namespace");
        }

        return instance;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IServicePlugin> T getPlatformService(Class<T> expectedInterface, ServiceScope scope, INamespace namespace) throws ServiceScopingException
    {
        T instance = null;

        Assert.parameterNotNull(expectedInterface, "Parameter 'expectedInterface' must not be null");
        Assert.parameterNotNull(scope, "Parameter 'scope' must not be null");

        switch (scope)
        {
            case ApplicationSingleton:
            case NewInstancePerRequest:
                // We ignore the namespace for these scopes- it can even be a null for all we care
                return getPlatformService(expectedInterface, scope);
            case FamilySingleton:
            case NamespaceSingleton:
            case ExtendedNamespaceSingleton:
                Assert.parameterNotNull(namespace, "Parameter 'namespace' must not be null when requesting a scope of " + scope);

                if (scope == ServiceScope.ExtendedNamespaceSingleton)
                {
                    Assert.parameterNotNull(namespace.getRefId(), "Parameter 'namespace' must include a reference ID when requesting a scope of " + ServiceScope.ExtendedNamespaceSingleton);
                }

                T newScopedInstance = (T) initializeService(expectedInterface);

                if (newScopedInstance.getRestrictedServiceScopes().contains(scope))
                {
                    throw new ServiceScopingException("Custom service " + Namespace.toCanonical(newScopedInstance) + " explicitly restricts the use of the scope " + scope);
                }

                Map<ServiceScope, List<ScopedServiceInstance>> scopeListMap = lookAtThisCrazyPhuckingMapDeclaration.get(expectedInterface);

                if (null != scopeListMap)
                {
                    List<ScopedServiceInstance> scopedServiceInstances = scopeListMap.get(scope);
                    if (null != scopedServiceInstances)
                    {
                        for (ScopedServiceInstance curInstance : scopedServiceInstances)
                        {
                            if (curInstance.getServiceScope() == scope)
                            {
                                switch (scope)
                                {
                                    case FamilySingleton:
                                        if (curInstance.getNamespace().getFamily().equals(namespace.getFamily()))
                                        {
                                            //noinspection unchecked
                                            instance = (T) curInstance.getInstance();
                                        }
                                        break;
                                    case NamespaceSingleton:
                                        if (curInstance.getNamespace().getFamily().equals(namespace.getFamily())
                                                && curInstance.getNamespace().getId().equals(namespace.getId()))
                                        {
                                            //noinspection unchecked
                                            instance = (T) curInstance.getInstance();
                                        }
                                        break;
                                    case ExtendedNamespaceSingleton:
                                        if (curInstance.getNamespace().getFamily().equals(namespace.getFamily())
                                                && curInstance.getNamespace().getId().equals(namespace.getId())
                                                && curInstance.getNamespace().getRefId().equals(namespace.getRefId()))
                                        {
                                            //noinspection unchecked
                                            instance = (T) curInstance.getInstance();
                                        }
                                        break;
                                }

                                if (null != instance)
                                {
                                    break;
                                }
                            }
                        }

                        if (null == instance)
                        {
                            // First request at this scope, but not the first request ever for this custom service plugin
                            newScopedInstance.setPlatformDelegate(delegate, IExtendedPlatformDelegate.class);
                            newScopedInstance.start();
                            ScopedServiceInstance<T> ssi = new ScopedServiceInstance<T>(expectedInterface, scope, namespace, newScopedInstance);

                            List<ScopedServiceInstance> list = new ArrayList<ScopedServiceInstance>();
                            list.add(ssi);

                            scopeListMap.put(scope, list);

                            instance = ssi.getInstance();
                        }
                    } else
                    {
                        throw new IllegalStateException("Service plugin was registered, but contained a null scoped service instance list");
                    }
                } else
                {
                    // First request ever (at any scope) for this custom service plugin
                    newScopedInstance.setPlatformDelegate(delegate, IExtendedPlatformDelegate.class);
                    newScopedInstance.start();
                    ScopedServiceInstance<T> ssi = new ScopedServiceInstance<T>(expectedInterface, scope, namespace, newScopedInstance);

                    List<ScopedServiceInstance> list = new ArrayList<ScopedServiceInstance>();
                    list.add(ssi);

                    Map<ServiceScope, List<ScopedServiceInstance>> serviceScopeMap = new HashMap<ServiceScope, List<ScopedServiceInstance>>();
                    serviceScopeMap.put(scope, list);

                    lookAtThisCrazyPhuckingMapDeclaration.put(expectedInterface, serviceScopeMap);

                    instance = ssi.getInstance();
                }
                break;
        }

        return instance;
    }

    private <T extends IServicePlugin> IServicePlugin initializeService(Class<T> serviceClass)
            throws ServiceInitializationException
    {
        IServicePlugin result = null;

        try
        {
            result = serviceClass.newInstance();
        } catch (InstantiationException e)
        {
            String msg = "Cannot instantiate class " + serviceClass + " found on the CLASSPATH";
            logger.severe(msg);
            throw new ServiceInitializationException(msg, e);
        } catch (IllegalAccessException e)
        {
            String msg = "Cannot access class " + serviceClass;
            logger.severe(msg);
            throw new ServiceInitializationException(msg, e);
        }

        return result;
    }
}
